//
// SecureShell
//
// Copyright (C) jextra.net.
//
//  This file is part of the Crack build system.
//
//  The Crack build system is free software; you can redistribute it 
//  and/or modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The Crack build system is distributed in the hope that it will be 
//  useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the Crack build system; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crack.tool;

import java.io.*;
import java.util.*;
import com.jcraft.jsch.*;

public class SecureShell
{
    // ============================================================
    // Fields
    // ============================================================

    private JSch jsch;
    private Session session;

    // ============================================================
    // Constructors
    // ============================================================

    public SecureShell( Credentials creds )
        throws JSchException, IOException
    {
        jsch = new JSch();
        jsch.addIdentity( creds.getPrivateKeyFile().getAbsolutePath() );

        session = jsch.getSession( creds.getUsername(), creds.getHost(), creds.getPort() );
        if ( creds.getPassword() != null )
        {
            session.setPassword( creds.getPassword() );
        }

        // Don't key check
        java.util.Properties config = new java.util.Properties();
        config.put( "StrictHostKeyChecking", "no" );
        session.setConfig( config );

        session.connect();
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    /**
     * executes a command and returns whatever the command returns as an int (exit status)
     * 
     * @param command
     * @return
     * @throws IOException
     * @throws JSchException
     */
    public int executeCommand( String command )
        throws IOException, JSchException
    {
        // Open a channel
        ChannelExec channel = (ChannelExec) session.openChannel( "exec" );

        // For a tty termial (so that sudo's work).
        channel.setPty( true );

        channel.setInputStream( null );
        channel.setOutputStream( System.out );
        channel.setErrStream( System.err );

        channel.setCommand( command );
        InputStream in = channel.getInputStream();
        channel.connect();

        byte[] buffer = new byte[1024];

        while ( true )
        {
            while ( in.available() > 0 )
            {
                int i = in.read( buffer, 0, 1024 );
                if ( i < 0 )
                {
                    break;
                }
                System.out.print( new String( buffer, 0, i ) );
            }

            if ( channel.isClosed() )
            {
                break;
            }

            try
            {
                Thread.sleep( 1000 );
            }
            catch ( Exception ee )
            {
                ee.printStackTrace();
            }
        }

        int exitValue = channel.getExitStatus();
        channel.disconnect();
        return exitValue;
    }

    /**
     * executes a command and returns whatever the command returns as an int (exit status)
     * 
     * @param command
     * @return
     * @throws IOException
     * @throws JSchException
     */
    public int executeCommand( List<String> command )
        throws IOException, JSchException
    {
        StringBuilder builder = new StringBuilder();

        for ( String arg : command )
        {
            builder.append( arg );
            builder.append( " " );
        }

        return executeCommand( builder.toString() );
    }

    /**
     * fromPath is the local path on the remote machine. toPath is the local path on the local machine.	
     * 
     * this method actually performs the copy, the file will exist local post call. 
     * 
     * @param fromPath
     * @param toPath
     * @return
     * @throws JSchException
     * @throws SftpException
     * @throws IOException 
     */
    public void downloadFile( String fromPath, File toFile )
        throws JSchException, SftpException, IOException
    {
        Channel channel = session.openChannel( "sftp" );
        channel.connect();
        ChannelSftp chsftp = (ChannelSftp) channel;

        InputStream in = chsftp.get( fromPath );

        OutputStream out = new FileOutputStream( toFile );
        byte[] buf = new byte[1024];
        int len = 0;

        while ( ( len = in.read( buf ) ) > 0 )
        {
            out.write( buf, 0, len );
        }

        out.close();
        in.close();

        channel.disconnect();
    }

    /**
     * the fromPath is the local path on the local machine. The toPath is the local path on the remote machine.
     * @param fromPath
     * @param toPath
     * @throws JSchException
     * @throws FileNotFoundException 
     * @throws SftpException 
     */
    public void uploadFile( File fromFile, String toPath )
        throws JSchException, FileNotFoundException, SftpException
    {
        Channel channel = session.openChannel( "sftp" );
        channel.connect();
        ChannelSftp chsftp = (ChannelSftp) channel;

        InputStream src = new FileInputStream( fromFile );

        chsftp.put( src, toPath );

        channel.disconnect();
    }

    /**
     * ends the ssh session, rendering the object rather useless and ready for garbage collection
     */
    public void disconnect()
    {
        if ( session.isConnected() )
        {
            session.disconnect();
        }
    }

    // ============================================================
    // Inner Classes
    // ============================================================

    public static class Credentials
    {
        private String host;
        private int port = 22;
        private String username;
        private String password;
        private File privateKeyFile;

        public String getHost()
        {
            return host;
        }

        public void setHost( String host )
        {
            this.host = host;
        }

        public int getPort()
        {
            return port;
        }

        public void setPort( int port )
        {
            this.port = port;
        }

        public String getUsername()
        {
            return username;
        }

        public void setUsername( String username )
        {
            this.username = username;
        }

        public String getPassword()
        {
            return password;
        }

        public void setPassword( String password )
        {
            this.password = password;
        }

        public File getPrivateKeyFile()
        {
            return privateKeyFile;
        }

        public void setPrivateKeyFile( File privateKeyFile )
        {
            this.privateKeyFile = privateKeyFile;
        }
    }
}
